import { _decorator, Component, instantiate, Node, NodePool, tween, Tween, UIOpacity, Widget } from 'cc';
import { BasePopUp } from '../../../frame/component/ui.pop-up';
import { EPopup, GetPopComponent, GetPopPrefab, PopIns, PopParams, PopReturn } from '../pop-up/popup.config';
const { ccclass, property } = _decorator;

@ccclass('Popup')
export class Popup extends Component {
	private static _ins: Popup;
	/** 全局唯一安全实例 */
	public static get ins(){
		return Popup._ins;
	}

	private hide_map: Map<EPopup, NodePool>;
	private shade_list: UIOpacity[];

	private opacity: number = 200;
	private duration: number = 0.25;
	private get dur_half(){
		return this.duration * 0.5;
	}

	protected onLoad(){
		Popup._ins = this;

		this.init();
	}

	private init(){
		this.node.active = false;
		this.hide_map = new Map();
		this.shade_list = this.node.children.filter(n=>n.name==='___shade').map(n=>n.getComponent(UIOpacity));
		this.shade_list.forEach(shade=>{
			shade.node.active = true;
			shade.opacity = 0;
		});
	}

	private async activePop<P extends EPopup>(p: P): Promise<PopIns<P>>{
		let node: Node;
		if(this.hide_map.has(p) && this.hide_map.get(p).size()>0) node = this.hide_map.get(p).get();
		else node = instantiate(await GetPopPrefab(p));

		node.active = false;
		node.setParent(this.node);

		//@ts-ignore
		return node.getComponent(await GetPopComponent(p));
	}

	private __shade_ref_: number = 0;
	private addShade(){
		let duration = this.duration * 1;
		this.__shade_ref_++;

		let slist = this.shade_list.map(shade=>{
			Tween.stopAllByTarget(shade);
			let wgt = shade.getComponent(Widget);
			if(wgt) wgt.updateAlignment();
			return {shade, z: shade.node.getSiblingIndex()};
		}).sort((a, b)=>b.z-a.z);

		let tw_list: Tween<UIOpacity>[] = [];

		if(this.__shade_ref_===1){
			slist.forEach(el=>el.shade.opacity = 0);
			tw_list.push(tween(slist[1].shade).to(duration, {opacity: this.opacity}));
		}else{
			let fixed_z = this.__shade_ref_-1;
			let fixed_i = slist.findIndex(el=>el.z===fixed_z);
			let fixed_shade: UIOpacity;
			if(fixed_i>=0) fixed_shade = slist.splice(fixed_i, 1)[0].shade;
			else{
				fixed_shade = slist.pop().shade;
				fixed_shade.node.setSiblingIndex(fixed_z);
			}

			slist[0].shade.node.setSiblingIndex(this.__shade_ref_);
			slist[0].shade.opacity = 0;
			fixed_shade.opacity = this.opacity;

			tw_list.push(tween(fixed_shade).to(duration, {opacity: 0}, {progress(start, end, current, ratio) {
				let total = start-end;
				return 255 * total * (1-ratio) / (255 - total * ratio);
			}}));
			tw_list.push(tween(slist[0].shade).to(duration, {opacity: this.opacity}))
		}

		tw_list.forEach(tw=>tw.start());
	}

	private cutShade(){
		let duration = this.duration * 1;
		this.__shade_ref_--;

		let slist = this.shade_list.map(shade=>{
			Tween.stopAllByTarget(shade);
			return {shade, z: shade.node.getSiblingIndex()};
		}).sort((a, b)=>b.z-a.z);

		let tw_list: Tween<UIOpacity>[] = [];

		if(this.__shade_ref_===0) tw_list.push(tween(slist[1].shade).to(duration, {opacity: 0}));
		else{
			let fixed_z = this.__shade_ref_-1;
			let fixed_i = slist.findIndex(el=>el.z===fixed_z);
			let fixed_shade: UIOpacity;
			if(fixed_i>=0) fixed_shade = slist.splice(fixed_i, 1)[0].shade;
			else{
				fixed_shade = slist.shift().shade;
				fixed_shade.node.setSiblingIndex(fixed_z);
			}
			fixed_shade.opacity = 0;
			slist[0].shade.opacity = this.opacity;
			slist[0].shade.node.setSiblingIndex(this.__shade_ref_+1);

			tw_list.push(tween(fixed_shade).to(duration, {opacity: this.opacity}, {progress(start, end, curr, ratio){
				let total = end-start;
				return 255 * total * ratio / (255 - total * (1-ratio));
			}}));
			tw_list.push(
				tween(slist[0].shade).to(duration, {opacity: 0})
				.call(()=>slist[0].shade.node.setSiblingIndex(this.__shade_ref_>1?this.__shade_ref_-2:0))
			);
		}

		tw_list.forEach(tw=>tw.start());
	}

	private on_going: EPopup[] = [];

	private async show<P extends EPopup>(p: P, ...args: PopParams<P>): Promise<PopReturn<P>>{
		if(this.on_going.includes(p)) return void 0;
		else this.on_going.push(p);

		if(!this.node.active) this.node.active = true;
		this.addShade();

		let pop = await this.activePop(p);
		mtec.log.tag(`SHOW-POP: chartreuse; ${pop.comp_name}: lightyellow;`);

		let result: any;
		if(pop){
			result = await pop.show(Popup, ...args);
			Popup.ins.close(p, pop);
		}else result = undefined;

		return result;
	}

	private close<P extends EPopup, C extends BasePopUp<typeof Popup, unknown>>(p: P, comp: C){
		if(this.on_going.includes(p)) mtec.array.remove(this.on_going, p);

		if(this.node.children.includes(comp.node)){
			mtec.log.tag(`CLOSE-POP: crimson; ${comp.comp_name}: lightyellow;`);
			this.cutShade();

			comp.node.active = false;
			if(!this.hide_map.has(p)) this.hide_map.set(p, new NodePool());
			this.hide_map.get(p).put(comp.node);
		}

		if(this.__shade_ref_<=0 && !(this.on_going.length>0)) this.node.active = false;
	}

	private static __lock__: boolean = false;
	public static get lock(){
		return Popup.__lock__;
	}
	public static set lock(v: boolean){
		Popup.__lock__ = v;
	}

	public static get hasPop(){
		return Popup.ins.node.children.length>2;
	}

	public static async Defeat(...args: PopParams<EPopup.DEFEAT>){
		return Popup.ins.show(EPopup.DEFEAT, ...args);
	}

	public static async Victory(...args: PopParams<EPopup.VICTORY>){
		return Popup.ins.show(EPopup.VICTORY, ...args);
	}

	public static async Diamond(...args: PopParams<EPopup.DIAMOND>){
		return Popup.ins.show(EPopup.DIAMOND, ...args);
	}

	public static async DrawerLock(...args: PopParams<EPopup.DRAWER_LOCK>){
		return Popup.ins.show(EPopup.DRAWER_LOCK, ...args);
	}

	public static async GiftBlocker(...args: PopParams<EPopup.GIFT_BLOCKER>){
		return Popup.ins.show(EPopup.GIFT_BLOCKER, ...args);
	}

	public static async Prop(...args: PopParams<EPopup.PROP>){
		return Popup.ins.show(EPopup.PROP, ...args);
	}

	public static async Resurgence(...args: PopParams<EPopup.RESURGENCE>){
		return Popup.ins.show(EPopup.RESURGENCE, ...args);
	}

	public static async Treasurebox(...args: PopParams<EPopup.TREASUREBOX>){
		return Popup.ins.show(EPopup.TREASUREBOX, ...args);
	}

	public static async Wait(...args: PopParams<EPopup.WAIT>){
		return Popup.ins.show(EPopup.WAIT, ...args);
	}

	public static async Congratulation(...args: PopParams<EPopup.CONGRATULATION>){
		return Popup.ins.show(EPopup.CONGRATULATION, ...args);
	}

	public static async DiamondFruit(...args: PopParams<EPopup.DIAMOND_FRUIT>){
		return Popup.ins.show(EPopup.DIAMOND_FRUIT, ...args);
	}

	public static async ActiveMaster(...args: PopParams<EPopup.ACTIVE_MASTER>){
		return Popup.ins.show(EPopup.ACTIVE_MASTER, ...args);
	}
}

